package com.gosling.cloudspace.tools;

import cn.hutool.core.io.IoUtil;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXValidator;
import org.dom4j.util.XMLErrorHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;

/**
 * XML工具类
 * @author lihonglin
 * @date 2022/4/15 16:36
 */
public class XmlTool {
   private final static Pattern COMPILE = Pattern.compile("\\s*|\t|\r|\n|\r\n");
   protected static Logger logger = LoggerFactory.getLogger(XmlTool.class);

   public static boolean xmlReadable(File xmlFile) {
      Element rootElement;
      try {
         SAXReader xmlReader = new SAXReader();
         Document document = xmlReader.read(xmlFile);
         rootElement = document.getRootElement();
      } catch (DocumentException e) {
         e.printStackTrace();
         return false;
      }
      return (rootElement != null);
   }

   public static Map<String, Map<String, String>> getSpecifiedNode(File xmlFile, String nodeName, boolean attribute) {
      Map<String, Map<String, String>> nodesMap = new HashMap<String, Map<String, String>>();
      try {
         SAXReader xmlReader = new SAXReader();
         Document document = xmlReader.read(xmlFile);
         Element rootElement = document.getRootElement();
         List<Element> elements = loopNode(rootElement, nodeName);
         for (Element element : elements) {
            List<Element> subElements = loopNode(element, null);
            List<Attribute> attributes = element.attributes();
            if (subElements.size() > 0) {
               for (Element subElement : subElements) {
                  Map<String, String> subMap = new HashMap<String, String>();
                  if (attribute) {
                     List<Attribute> subAttributes = subElement.attributes();
                     for (Attribute attr : attributes) {
                        String name = attr.getName();
                        String nodeValue = attr.getText();
                        subMap.put(name, nodeValue);
                     }
                     for (Attribute attr : subAttributes) {
                        String name = attr.getName();
                        String nodeValue = attr.getText();
                        subMap.put(name, nodeValue);
                     }
                  }
                  String elementName = subElement.getName();
                  String elementText = subElement.getText();
                  subMap.put(elementName, elementText);
                  nodesMap.put(elementName, subMap);
               }
               continue;
            }
            Map<String, String> subMap = new HashMap<String, String>();
            if (attribute)
               for (Attribute attr : attributes) {
                  String name = attr.getName();
                  String nodeValue = attr.getText();
                  subMap.put(name, nodeValue);
               }
            String elementName = element.getName();
            String elementText = element.getText();
            subMap.put(elementName, elementText);
            nodesMap.put(elementName, subMap);
         }
      } catch (Exception e) {
         e.printStackTrace();
         return nodesMap;
      }
      return nodesMap;
   }

   public static List<Map<String, String>> getMultiNode(File xmlFile, String nodeName, boolean attribute) {
      List<Map<String, String>> nodesList = new ArrayList<Map<String, String>>();
      try {
         SAXReader xmlReader = new SAXReader();
         Document document = xmlReader.read(xmlFile);
         Element rootElement = document.getRootElement();
         List<Element> elements = loopNode(rootElement, nodeName);
         for (Element element : elements) {
            List<Element> subElements = loopNode(element, null);
            List<Attribute> attributes = element.attributes();
            if (subElements.size() > 0) {
               for (Element subElement : subElements) {
                  Map<String, String> subMap = new HashMap<String, String>();
                  if (attribute) {
                     List<Attribute> subAttributes = subElement.attributes();
                     for (Attribute attr : subAttributes) {
                        String name = attr.getName();
                        String nodeValue = attr.getText();
                        subMap.put(name, nodeValue);
                     }
                  }
                  String elementName = subElement.getName();
                  String elementText = subElement.getText();
                  subMap.put(elementName, elementText);
                  nodesList.add(subMap);
               }
               continue;
            }
            Map<String, String> subMap = new HashMap<String, String>();
            if (attribute)
               for (Attribute attr : attributes) {
                  String name = attr.getName();
                  String nodeValue = attr.getText();
                  subMap.put(name, nodeValue);
               }
            String elementName = element.getName();
            String elementText = element.getText();
            subMap.put(elementName, elementText);
            nodesList.add(subMap);
         }
      } catch (Exception e) {
         return nodesList;
      }
      return nodesList;
   }

   private static List<Element> loopNode(Element node, String name) {
      List<Element> subElements;
      if (StringUtils.isBlank(name)) {
         subElements = node.selectNodes("descendant::*[count(child::*)=0]");
      } else {
         subElements = node.selectNodes("descendant::*[name()='" + name + "']");
      }
      return subElements;
   }

   /**
    * logger.info获取文件信息
    *
    * @param e
    * @return Map
    * @email 1793327033@qq.com
    * @classname XmlTool
    * @author: osc_lihonglin
    * @date: 2021/7/30 16:50
    */
   public static Map readEle(Element e, LinkedHashMap map) {
      //判断是否有复合内容
      if (e.hasMixedContent()) {
         //输出该节点的名字，对他的子节点继续进行判断
         logger.debug("节点名：" + e.getName());
         Iterator it = e.elementIterator();
         while (it.hasNext()) {
            Element arrrName = (Element) it.next();
            //递归
            map.put(e.getName(), e.getTextTrim());
            readEle(arrrName, map);
         }
      } else {
         //如果没有复合内容，就可以输出了
         logger.debug("节点名：" + e.getName() + "，节点值：" + e.getTextTrim());
      }
      return map;
   }

   public static List<Map<String, String>> getMultiNodeMap(File xmlFile, String nodeName, boolean attribute) {
      List<Map<String, String>> nodesList = new ArrayList<Map<String, String>>();
      try {
         SAXReader xmlReader = new SAXReader();
         Document document = xmlReader.read(xmlFile);
         Element rootElement = document.getRootElement();
         List<Element> elements = loopNode(rootElement, nodeName);
         for (Element element : elements) {
            List<Element> subElements = loopNode(element, null);
            Map<String, String> subMap = new HashMap<String, String>();
            List<Attribute> attributes = element.attributes();
            if (subElements.size() > 0) {
               for (Element subElement : subElements) {
                  if (attribute) {
                     List<Attribute> subAttributes = subElement.attributes();
                     for (Attribute attr : subAttributes) {
                        String name = attr.getName();
                        String nodeValue = attr.getText();
                        subMap.put(name, nodeValue);
                     }
                  }
                  String elementName = subElement.getName();
                  String elementText = subElement.getText();
                  subMap.put(elementName, elementText);
               }
               nodesList.add(subMap);
               continue;
            }
            if (attribute)
               for (Attribute attr : attributes) {
                  String name = attr.getName();
                  String nodeValue = attr.getText();
                  subMap.put(name, nodeValue);
               }
            String elementName = element.getName();
            String elementText = element.getText();
            subMap.put(elementName, elementText);
            nodesList.add(subMap);
         }
      } catch (Exception e) {
         return nodesList;
      }
      return nodesList;
   }

   public static Element getRootElement(File file) {
      SAXReader saxReader = new SAXReader();
      try {
         Document document = saxReader.read(file);
         return document.getRootElement();
      } catch (DocumentException e) {
         e.printStackTrace();
      } finally {
      }
      return null;
   }

   public static Map validateXMLByXSD(File xsdFile, String xmlFilePath) {
      Map result = new HashMap();
      try {
         XMLErrorHandler errorHandler = new XMLErrorHandler();
         SAXParserFactory factory = SAXParserFactory.newInstance();
         factory.setValidating(true);
         factory.setNamespaceAware(true);
         SAXParser parser = factory.newSAXParser();
         SAXReader xmlReader = new SAXReader();
         Document xmlDocument = xmlReader.read(new File(xmlFilePath));
         parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
         parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", "file:" + xsdFile);
         SAXValidator validator = new SAXValidator(parser.getXMLReader());
         validator.setErrorHandler(errorHandler);
         validator.validate(xmlDocument);
         OutputFormat format = OutputFormat.createPrettyPrint();
         format.setEncoding("UTF-8");
         if (errorHandler.getErrors().hasContent()) {
            result.put("state", Boolean.valueOf(false));
            result.put("reason", errorHandler.getErrors().asXML());
            return result;
         }
         result.put("state", Boolean.valueOf(true));
         return result;
      } catch (Exception ex) {
         result.put("state", Boolean.valueOf(false));
         result.put("reason", "XML文件: " + xmlFilePath + " 通过XSD文件:" + xsdFile + "检验失败。/n原因： " + ex.getMessage());
         ex.printStackTrace();
         return result;
      }
   }

   /***
    * 替换xml中 xmlns="" 的情况
    *
    * @param document
    * @return
    * @throws DocumentException
    */
   public static Document asXmlDocument(Document document) {
      ByteArrayInputStream in = null;
      Document read = null;
      try {
         String asXml = document.getRootElement().asXML();
         in = new ByteArrayInputStream(asXml.replace("xmlns=\"\"", "").getBytes());
         read = new SAXReader().read(in);
         in.close();
         logger.debug("替换xml中多余 xmlns=“” 为“” 成功");
         return read;
      } catch (Exception e) {
         logger.debug("替换xml中多余 xmlns=“” 为“” 失败");
         try {
            if (in != null) {
               in.close();
            }
         } catch (Exception ex) {
            ex.printStackTrace();
         }
         return document;
      }
   }

   /***
    * 清理掉 xmlns
    *
    * @param elem
    * @param ns
    */
   public static void setNamespace(Element elem, Namespace ns) {
      elem.setQName(QName.get(elem.getName(), ns, elem.getQualifiedName()));
   }

   /***
    * 获取xml中的需要加签对象的内容
    * (电子文件封装包所封装的编码数据（M221）不应加密)
    *
    * @author: osc_lihonglin
    * @date: 2021/11/18 13:16
    * @param document
    * @return
    */
   public static String getContentByElement(Document document) {
      Element root = document.getRootElement();
      //删除掉XML中的编码数据用于加密
      removeXmlBase(root);
      logger.debug("获取新生成xml中的需要加签对象的内容");
      return root.element("被签名对象").asXML().trim();
   }

   /***
    * 获取xml中的需要加签对象的内容
    * (电子文件封装包所封装的编码数据（M221）不应加密)
    *
    * @author: osc_lihonglin
    * @date: 2021/11/18 13:16
    * @return
    */
   public static String getContentByDocument(Element root) {
      //删除掉XML中的编码数据用于加密
      removeXmlBase(root);
      logger.debug("获取新生成xml中的需要加签对象的内容");
      return root.element("被签名对象").asXML().trim();
   }

   /***
    * 获取xml中的需要加签对象的内容
    * (电子文件封装包所封装的编码数据（M221）不应加密)
    *
    * @author: osc_lihonglin
    * @date: 2021/11/18 13:16
    * @param file
    * @return
    */
   public static String getContentByFile(File file) throws DocumentException {
      Document document = new SAXReader().read(file);
      Element root = document.getRootElement();
      //删除掉XML中的编码数据用于加密
      removeXmlBase(root);
      return COMPILE.matcher(root.element("被签名对象").asXML().trim()).replaceAll("");
   }

   /***
    * 删除掉XML中的编码数据用于加密
    *
    * @author: osc_lihonglin
    * @date: 2021/11/18 13:16
    * @param root
    */
   public static void removeXmlBase(Element root) {
      boolean equals = root.element("被签名对象").element("封装包类型").getText().trim().equals("原始型");
      if (equals) {
         removeBaseData(root);
      } else {
         Element e = root.element("被签名对象").element("修改封装内容").element("修订内容");
         Element o = root.element("被签名对象").element("修改封装内容").element("原封装包");
         List elements = e.element("文件实体块").element("文件数据").elements("文档");
         removeDocument(elements, o);
      }
   }

   /***
    * 删除编码数据
    *
    * @author: osc_lihonglin
    * @date: 2021/11/18 13:16
    * @param root
    */
   private static void removeBaseData(Element root) {
      Element entry = root.element("被签名对象");
      if (!Objects.isNull(entry)) {
         Element content = entry.element("封装内容");
         if (!Objects.isNull(content)) {
            Element r = content.element("文件实体块");
            if (!Objects.isNull(r)) {
               Element fileData = r.element("文件数据");
               if (!Objects.isNull(fileData)) {
                  List<Element> elements = fileData.elements("文档");
                  if (!Objects.isNull(elements) && elements.size() > 0) {
                     for (Element element : elements) {
                        Element e = element.element("文档数据");
                        if (!Objects.isNull(e)) {
                           Element base64Data = e.element("编码数据");
                           if (!Objects.isNull(base64Data)) {
                              String c = base64Data.getText();
                              if (StringUtils.isNotBlank(c)) {
                                 e.remove(base64Data);
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   /***
    * 递归删除编码数据获取加签内容
    *
    * @author: osc_lihonglin
    * @date: 2021/11/18 13:16
    * @param elementsList
    * @param originRoot
    */
   private static void removeDocument(List<Element> elementsList, Element originRoot) {
      for (int j = 0; j < elementsList.size(); j++) {
         Element documentData = elementsList.get(j).element("文档数据");
         if (!Objects.isNull(documentData)) {
            Element docBase64Data = documentData.element("编码数据");
            if (!Objects.isNull(docBase64Data)) {
               //删除编码数据内容
               documentData.remove(docBase64Data);
               Element entry = originRoot.element("被签名对象");
               if (!Objects.isNull(entry)) {
                  Element editEntry = entry.element("修改封装内容");
                  Element t = entry.element("封装包类型");
                  if (!Objects.isNull(t)) {
                     boolean equals = t.getText().equals("修改型");
                     if (equals) {
                        if (!Objects.isNull(editEntry)) {
                           Element e = editEntry.element("修订内容");
                           if (!Objects.isNull(e)) {
                              Element fileEntry = e.element("文件实体块");
                              if (!Objects.isNull(fileEntry)) {
                                 Element fileData = fileEntry.element("文件数据");
                                 if (!Objects.isNull(fileData)) {
                                    List<Element> k = fileData.elements("文档");
                                    if (!Objects.isNull(k) && k.size() > 0) {
                                       Element oldPack = editEntry.element("原封装包");
                                       if (!Objects.isNull(oldPack)) {
                                          //递归删除编码数据内容
                                          removeDocument(k, oldPack);
                                       }
                                    }
                                 }
                              }
                           }
                        }
                     } else {
                        //获取修改内容失败即为最原始的封装包
                        Element packContent = entry.element("封装内容");
                        if (!Objects.isNull(packContent)) {
                           Element fileEntry = packContent.element("文件实体块");
                           if (!Objects.isNull(fileEntry)) {
                              Element file2Data = fileEntry.element("文件数据");
                              if (!Objects.isNull(file2Data)) {
                                 List<Element> k = file2Data.elements("文档");
                                 if (!Objects.isNull(k)) {
                                    if (k.size() > 0) {
                                       for (int i = 0; i < k.size(); i++) {
                                          documentData = k.get(i).element("文档数据");
                                          if (!Objects.isNull(documentData)) {
                                             docBase64Data = documentData.element("编码数据");
                                             if (!Objects.isNull(docBase64Data)) {
                                                documentData.remove(docBase64Data);
                                             }
                                          }
                                       }
                                    }
                                 }
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   /**
    * 递归读取元素内容
    * 直到最原始编码数据ID和现在的主键ID相等为止
    *
    * @param originRoot
    * @param id
    * @email 1793327033@qq.com
    * @classname XmlTool
    * @author: osc_lihonglin
    * @date: 2021/11/30 13:12
    */
   public static Element readDocument(Element originRoot, String id) {
      Element signData = originRoot.element("被签名对象");
      if (!Objects.isNull(signData)) {
         Element editContent = signData.element("修改封装内容");
         if (!Objects.isNull(editContent)) {
            Element e = editContent.element("修订内容");
            if (!Objects.isNull(e)) {
               Element fileEntry = e.element("文件实体块");
               if (!Objects.isNull(fileEntry)) {
                  Element fileData = fileEntry.element("文件数据");
                  if (!Objects.isNull(fileData)) {
                     List<Element> k = fileData.elements("文档");
                     if (!Objects.isNull(k) && k.size() > 0) {
                        //循环修改数据下的文档
                        Element element = elecElement(k, id);
                        if (!Objects.isNull(element)) {
                           return element;
                        } else {
                           Element oldPack = editContent.element("原封装包");
                           if (!Objects.isNull(oldPack)) {
                              return readDocument(oldPack, id);
                           }
                        }
                     }
                  }
               }
            }
         } else {
            //获取修改内容失败即为最原始的封装包
            Element packContent = signData.element("封装内容");
            if (!Objects.isNull(packContent)) {
               Element fileEntry = packContent.element("文件实体块");
               if (!Objects.isNull(fileEntry)) {
                  Element file2Data = fileEntry.element("文件数据");
                  if (!Objects.isNull(file2Data)) {
                     List<Element> k = file2Data.elements("文档");
                     if (!Objects.isNull(k) && k.size() > 0) {
                        //循环原始数据下的文档
                        return elecElement(k, id);
                     } else {
                        return null;
                     }
                  } else {
                     return null;
                  }
               }
            }
         }
      }
      return null;
   }

   /***
    * 文档数据循环体
    *
    * @param elementsList
    * @param id
    * @return
    */
   private static Element elecElement(List<Element> elementsList, String id) {
      for (int j = 0; j < elementsList.size(); j++) {
         Element f = elementsList.get(j).element("文档数据");
         if (!Objects.isNull(f)) {
            Element docBase64Data = f.element("编码数据");
            if (!Objects.isNull(docBase64Data)) {
               Attribute attribute = docBase64Data.attribute("编码数据ID");
               if (!Objects.isNull(attribute)) {
                  String base64Id = attribute.getValue();
                  if (id.equals(base64Id)) {
                     logger.debug("链路追踪能找到最原始的电子文件");
                     return elementsList.get(j);
                  }
               }
            }
         }
      }
      return null;
   }

   /**
    * 输入流转换为xml字符串
    * @param inputStream
    * @return
    */
   public static String convertToString(InputStream inputStream) throws IOException {
      ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
      try {
         byte[] buffer = new byte[1024];
         int len = 0;
         while ((len = inputStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
         }
      } catch (IOException e) {
         e.printStackTrace();
      } finally {
         IOTool.closeQuietly(outSteam);
         IoUtil.close(inputStream);
      }
      return new String(outSteam.toByteArray(), StandardCharsets.UTF_8);
   }

}
